package br.art.late.openenvironment.boids;

import java.util.ArrayList;

import processing.core.PVector;
import br.art.late.openenvironment.boids.Zone;
import br.art.late.openenvironment.main.OpenEnvironment;


public class BoidsFlock
{
	// Simulation zone
	public Zone zone;
	// Keep in zone by looping or bouncing
	boolean loopZone;
	// Boids list
	ArrayList list;
	// Boids try to approach other boids (factor)
	float cohesion;
	// Boids try to keep a minimum distance between others (factor)
	float avoidance;
	// Boids try to move in the same way than other boids (factor)
	float imitation;
	// Boids radius
	int size = 3;
	// Distance for which boids touch things (avoidance, collisions)
	float aura;
	// Distance for which boids sees and responds to its environment
	float perception;
	// Define cruise-speed and stress-speed limit
	float speedLimit; 

	OpenEnvironment parent;

	/**
	 * Class constructor.
	 */
	public BoidsFlock(OpenEnvironment parent)
	{
		this.parent = parent;
		zone = new Zone(0, 0, parent.width, parent.height, parent);
		loopZone = true;
		list = new ArrayList();
		cohesion = 10;
		avoidance = 25;
		imitation = 10;
		size = 3;
		aura = 2 * size;
		perception = 15;
		speedLimit = 1;
	}


	/**
	 * Add a boid to flock.
	 */
	public void add(){
		list.add(new Boid(parent)); 
	}
	/**
	 * Add a boid to flock at the defined position.
	 */
	public void add(int x, int y){
		list.add(new Boid(parent, x, y)); 
	}
	/**
	 * Add a boid to flock at the defined position.
	 */
	public void add(PVector p){
		list.add(new Boid(parent, p)); 
	}

	/**
	 * Add a boid to flock outside object list auras or don't create it.
	 *
	 * @param  objs Obj ArrayList : list of objects which areas should be avoided
	 */
	public void addOutside(ArrayList objs)
	{
		boolean collide = false;
		int it = 0, itMax = 50000;
		PVector pos;
		// do this while random pos is in Obj zone
		do{
			it++;
			pos = parent.getBoids().zone.randomPos();
			for(int i=0; i<objs.size(); i++)
			{
				Obj o = (Obj) objs.get(i);
				if(PVector.dist(pos, o.getPos()) < o.getAura())
				{
					collide = true;
					break;
				}
			}
		}  
		while(collide && it < itMax);
		if(!collide) list.add(new Boid(parent,pos)); 
	}

	/**
	 * Delete boid from flock.
	 *
	 * @param  i boid index
	 */
	public void del(int i){
		list.remove(i);
	}

	/**
	 * Return the number of boids in flock.
	 */
	public int size(){
		return list.size();
	}

	/**
	 * Return the specified boid.
	 *
	 * @param i boid index
	 * @return Boid
	 */
	public Boid get(int i){
		return (Boid) list.get(i);
	}

	/**
	 * Update each boid position and velocity according to simulation rules.
	 */
	public void update()
	{
		for(int i=0; i<list.size(); i++)
		{
			Boid b = (Boid) list.get(i);

			// basic flock rules
			b.goToCenter();
			b.keepDistance();
			b.matchVelocity();

			// additionnal rules
			b.keepInZone(zone, loopZone);

			// Cruise-speed limitation
			b.limitVelocity(speedLimit);

			// food attraction and eat
			boolean contact;
			for(int j=0; j<parent.getFoodList().size(); j++)
			{ 
				Obj f = (Obj) parent.getFoodList().get(j);
				// medium attraction when perceived
				b.effector(parent.parseInt(f.getPos().x), parent.parseInt(f.getPos().y), f.getAura() + parent.getBoids().perception, 1.2f);
				// big repulsion on contact
				b.effector(parent.parseInt(f.getPos().x), parent.parseInt(f.getPos().y), f.getRad() + parent.getBoids().size, -40);
				// if contact
				if(PVector.dist(f.getPos(), b.pos) < f.getRad() + parent.getBoids().size)
				{
					f.setRad(f.getRad() - .5f);// reduce obj size
					// if size is too small delete it and update control value
					if(f.getRad() <= 1){
						parent.getFoodList().remove(j);
						//Slider s = (Slider) controlP5.controller("food");
						//s.setValue(foodList.size());
					}
				}
			}

			// obstacles boids.avoidance
			for(int j=0; j<parent.getObstList().size(); j++)
			{ 
				Obj o = (Obj) parent.getObstList().get(j);
				// small repulsion when perceived (anticipation)
				b.effector(parent.parseInt(o.getPos().x), parent.parseInt(o.getPos().y), o.getAura() + parent.getBoids().perception, -0.2f);
				// big repulsion on contact (collision)
				b.effector(parent.parseInt(o.getPos().x), parent.parseInt(o.getPos().y), o.getRad() + parent.getBoids().size, -20);
			}

			// mouse repulsion
			//b.effector(parent.mouseX, parent.mouseY, 70, -7);

			// Max-speed limitation
			//b.limitVelocity(speedLimit * 2);

			// finally update position
			b.pos.add(b.vel);
		}
	}

	/**
	 * Draw each boid.
	 */
	public void display()
	{
		for(int i=0; i < list.size(); i++)
		{
			Boid b = (Boid) list.get(i);
			b.display();
		} 
	}

	/**
	 * Draw perception distance of boid alpha.
	 */
	public void displayPerception()
	{
		parent.fill(150, 180, 255, 50); 
		//parent.stroke(150, 180, 255);
		Boid b = (Boid) parent.getBoids().get(0);
		parent.ellipse(b.pos.x,  b.pos.y, parent.getBoids().perception * 2, parent.getBoids().perception * 2);
	}

	/**
	 * Draw aura distance of boid alpha.
	 */
	public void displayAura()
	{
		parent.fill(255, 180, 150, 50); 
		//parent.stroke(255, 180, 150);
		Boid b = (Boid) parent.getBoids().get(0);
		parent.ellipse(b.pos.x,  b.pos.y, parent.getBoids().aura * 2, parent.getBoids().aura * 2);
	}


	// sliders effects

	public void boids(int n) {
		// add boids
		if(n > parent.getBoids().size()){
			while(parent.getBoids().size()<n){
				parent.getBoids().add();
			}
		}
		// remove boids
		else if(n<parent.getBoids().size()){
			while(parent.getBoids().size()>n){
				parent.getBoids().del(0);
			} 
		}
	}

	public void food(int n) {
		// add food
		if(n>parent.getFoodList().size()){
			while(parent.getFoodList().size()<n){
				parent.getFoodList().add(new Obj(parent));
			}
		}
		// remove food
		else if(n<parent.getFoodList().size()){
			while(parent.getFoodList().size()>n){
				parent.getFoodList().remove(0);
			} 
		}
	}

	public void obst(int n){
		// add obstacle
		if(n>parent.getObstList().size()){
			while(parent.getObstList().size()<n){
				parent.getObstList().add(new Obj(parent));
			}
		}
		// remove obstacle
		else if(n<parent.getObstList().size()){
			while(parent.getObstList().size()>n){
				parent.getObstList().remove(0);
			} 
		}
	}

	public void cohesion(float n){
		parent.getBoids().cohesion = n;
	}
	public void avoidance(float n){
		parent.getBoids().avoidance = n;
	}
	public void imitation(float n){
		parent.getBoids().imitation = n;
	}

	public void size(int n){
		parent.getBoids().size = n;
		parent.getBoids().aura = 3 * n;
		parent.getBoids().perception = 10 * n;
	}

}


